/*
 * Copyright (C) 2014 Wei Chou (weichou2010@gmail.com)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.wei.c.widget;

import android.content.Context;
import android.content.res.TypedArray;
import android.os.SystemClock;
import android.util.AttributeSet;
import android.util.SparseArray;
import android.util.SparseIntArray;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.animation.AnimationUtils;
import android.view.animation.CycleInterpolator;
import android.view.animation.Interpolator;
import android.widget.FrameLayout;
import android.widget.Scroller;

import com.wei.c.lib.R;

/**
 * 可侧滑停靠并显示底层的View。
 * 为了使第0层的View在第1层View显示的同时不会响应点击事件，给第1层设置了clickable。
 * 但是有个问题，设置clickable之后，本View的super.dispatchTouchEvent(ev)总是返回true，表示消化了事件，
 * 导致本View不知该何时去拦截事件以响应点击事件，可能子View需要处理点击事件。
 * 因此不要给本View设置点击事件，应该设置给第1层。
 * 
 * @author 周伟 Wei Chou(weichou2010@gmail.com)
 */
public class SideSlideView extends FrameLayout {
	public static final int MODE_STICKY_BACK			= 0;
	public static final int MODE_DRAG_REMOVE			= 1;

	static final int ANIMATED_SCROLL_GAP				= 250;
	static final int SLIDE_ANIM_DURATION				= 600;
	static final int SHAKE_ANIM_DURATION				= 350;
	static final float STICKY_DIVISOR					= 0.6f;

	/**左右滚动距离的累加和**/
	private int mCurrOffsetLeftAndRight, mDragChildCurrOffsetLeftAndRight;
	private int mTouchSlop;
	private int mMode = MODE_STICKY_BACK;
	private int mDownMotionX, mDownScrollX, mScrollByX;
	private long mDownTime, mLastScroll;
	private boolean mScrolling = false, mTapTimeout = false, mAnimFinished = true, mDidMoveBack = false;
	private View mSlideChild, mDragChild;
	private int mSlideAlignLeftViewId, mSlideAlignRightViewId;
	private int mSlideAlignLeftViewLeftSideViewId, mSlideAlignRightViewRightSideViewId;
	private int mSlideAlignLeftViewParentId, mSlideAlignRightViewParentId;
	private int mSlideShakeLeftHideViewId, mSlideShakeRightHideViewId, mShakeWidthReferViewId;
	private int mSlideOffsetOnLeftView, mSlideOffsetOnRightView;
	private float mStickyDivisor = STICKY_DIVISOR;
	private int mSlideOffsetLeft = -1, mSlideOffsetRight = 1;
	private int mSlideAnimDuration = SLIDE_ANIM_DURATION;
	private int mShakeAnimDuration = SHAKE_ANIM_DURATION;
	/**0：复位，-1：左移，1：右移**/
	private int mSlideDirection, mDragDirection, mScrollMode;
	private boolean mDeliverScrollEvent = false;
	private OnScrollListener mOnScrollListener;
	private Scroller mScroller, mShakeScroller;
	private boolean mDisallowInterceptRequest = false, mShaking = false;
	private SparseIntArray mWidthMap = new SparseIntArray();
	private SparseArray<View> mViewMap = new SparseArray<View>();

	public SideSlideView(Context context) {
		super(context);
		mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
		setInterpolator(null);
		reset();
	}

	public SideSlideView(Context context, AttributeSet attrs) {
		this(context, attrs, 0);
	}

	public SideSlideView(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();

		TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SideSlideView, defStyle, 0);

		setSlideMode(a.getInt(R.styleable.SideSlideView_slideMode, MODE_STICKY_BACK));
		mSlideAlignLeftViewId = a.getResourceId(R.styleable.SideSlideView_slideAlignLeftView, 0);
		mSlideAlignRightViewId = a.getResourceId(R.styleable.SideSlideView_slideAlignRightView, 0);
		mSlideAlignLeftViewLeftSideViewId = a.getResourceId(R.styleable.SideSlideView_slideAlignLeftViewLeftSideView, 0);
		mSlideAlignRightViewRightSideViewId = a.getResourceId(R.styleable.SideSlideView_slideAlignRightViewRightSideView, 0);
		mSlideShakeLeftHideViewId = a.getResourceId(R.styleable.SideSlideView_slideShakeLeftHideView, 0);
		mSlideShakeRightHideViewId = a.getResourceId(R.styleable.SideSlideView_slideShakeRightHideView, 0);
		mShakeWidthReferViewId = a.getResourceId(R.styleable.SideSlideView_slideShakeWidthReferView, 0);
		mSlideAlignLeftViewParentId = a.getResourceId(R.styleable.SideSlideView_slideAlignLeftViewParent, 0);
		mSlideAlignRightViewParentId = a.getResourceId(R.styleable.SideSlideView_slideAlignRightViewParent, 0);
		mSlideOffsetOnLeftView = a.getDimensionPixelOffset(R.styleable.SideSlideView_slideOffsetOnLeftView, 0);
		mSlideOffsetOnRightView = a.getDimensionPixelOffset(R.styleable.SideSlideView_slideOffsetOnRightView, 0);
		mStickyDivisor = a.getFloat(R.styleable.SideSlideView_slideStickyDivisor, STICKY_DIVISOR);
		setSlideAnimDuration(a.getInt(R.styleable.SideSlideView_slideAnimDuration, SLIDE_ANIM_DURATION));
		setShakeAnimDuration(a.getInt(R.styleable.SideSlideView_shakeAnimDuration, SHAKE_ANIM_DURATION));
		setInterpolator(a.getResourceId(R.styleable.SideSlideView_slideInterpolator, 0));

		a.recycle();
		reset();
	}

	public void setInterpolator(int interpolatorId) {
		Interpolator interpolator = null;
		if (interpolatorId > 0) try {
			interpolator = AnimationUtils.loadInterpolator(getContext(), interpolatorId);
		}catch(Exception e) {
			e.printStackTrace();
		}
		setInterpolator(interpolator);
	}

	public void setInterpolator(Interpolator interpolator) {
		mScroller = new Scroller(getContext(), interpolator);
	}

	public void setSlideAnimDuration(int duration) {
		mSlideAnimDuration = duration <= 0 ? SLIDE_ANIM_DURATION : duration;
	}

	public void setShakeAnimDuration(int duration) {
		mShakeAnimDuration = duration <= 0 ? SHAKE_ANIM_DURATION : duration;
	}

	public void setSlideMode(int mode) {
		if (mode < MODE_STICKY_BACK || mode > MODE_DRAG_REMOVE) throw new IllegalArgumentException("参数不合法，见常量");
		mMode = mode;
	}

	/**
	 * @param direction 0：复位，-1：左移，1：右移
	 */
	public void setSlideDirection(int direction) {
		mSlideDirection = direction;
		stopShakeInner();
		moveBackDirectly();
	}

	public void startShake(int timeDelay) {
		postDelayed(mShakeRun, timeDelay);
	}

	public void stopShake() {
		stopShakeInner();
		mSlideDirection = 0;
		moveBack();
	}

	private void stopShakeInner() {
		removeCallbacks(mShakeRun);
		if (mShakeScroller != null && !mShakeScroller.isFinished()) {
			mShakeScroller.abortAnimation();
			onShakeAbort();
		}
	}

	private Runnable mShakeRun = new Runnable() {
		@Override
		public void run() {
			stopShakeInner();
			mSlideDirection = 0;
			moveBackDirectly();
			if (mShakeScroller == null) mShakeScroller = new Scroller(getContext(), new CycleInterpolator(1));
			onPreShake();
			mShakeScroller.startScroll(0, 0, getShakeWidth(), 0, mShakeAnimDuration);
			invalidate();
		}
	};

	@Override
	public void addView(View child) {
		checkChildCount();
		super.addView(child);
		setSlideViewClickable();
	}

	@Override
	public void addView(View child, int index) {
		checkChildCount();
		super.addView(child, index);
		setSlideViewClickable();
	}

	@Override
	public void addView(View child, ViewGroup.LayoutParams params) {
		checkChildCount();
		super.addView(child, params);
		setSlideViewClickable();
	}

	@Override
	public void addView(View child, int index, ViewGroup.LayoutParams params) {
		checkChildCount();
		super.addView(child, index, params);
		setSlideViewClickable();
	}

	private void checkChildCount() {
		if (getChildCount() >= 2) {
			throw new IllegalStateException("SideSlideView只能有两个直接子View");
		}
	}

	/**为了使第0层的View在第1层View显示的同时不会响应点击事件，给第1层设置了clickable。
	 * 但是有个问题，设置clickable之后，本View的super.dispatchTouchEvent(ev)总是返回true，表示消化了事件，
	 * 导致本View不知该何时去拦截事件以响应点击事件，可能子View需要处理点击事件。
	 * 因此不要给本View设置点击事件，应该设置给第1层。**/
	private void setSlideViewClickable() {
		if(canSlide()) getSlideChild().setClickable(true);
	}

	private boolean canSlide() {
		return getChildCount() == 2 && isEnabled();
	}

	@Override
	public boolean dispatchTouchEvent(MotionEvent ev) {
		if(canSlide()) {
			boolean consume = false;
			switch (ev.getAction()) {
			case MotionEvent.ACTION_DOWN:
				mAnimFinished = mScroller.isFinished();
				if (!mAnimFinished) {
					mScroller.abortAnimation();
				}
				mScrolling = false;
				mTapTimeout = false;
				mDidMoveBack = false;
				mDisallowInterceptRequest = false;
				mDownMotionX = (int)ev.getX();
				//点击停止滚动，并没有归零
				mDownScrollX = mCurrOffsetLeftAndRight;
				mDownTime = SystemClock.elapsedRealtime();
				super.dispatchTouchEvent(ev);
				consume = true;
				break;
			case MotionEvent.ACTION_MOVE:
				mScrollByX = (int)ev.getX() - mDownMotionX;
				consume = super.dispatchTouchEvent(ev);
				if(mScrolling) {
					requestDisallowIntercept();
				}else {	//无论是否拦截，都应先移动
					/* 可能子View在ACTION_DOWN事件没有消化，则这里没走onInterceptTouchEvent()，
					 * 这种情况下mTapTimeout将一直为false，不过没关系，mScrolling为true了，走不到这个case来。
					 */
					if(mTapTimeout) {
						/* mAnimFinished，注意区别UP事件：
						 * 为true表示由于不是在动画执行期间中断的，该点击的意图可能只是上下滚动，那么回去吧；
						 * 若为false，表示正在执行动画，但是点击中止了，此时应该可以继续拖动
						 */
						if(mAnimFinished) {
							if (!mDidMoveBack) {
								moveBack();
								mDidMoveBack = true;
							}
						}else {
							requestDisallowIntercept();
							doDrag();
							consume = true;
						}
					}else {
						doDrag();
						consume = true;
					}
				}
				break;
			case MotionEvent.ACTION_UP:
			case MotionEvent.ACTION_CANCEL:
				consume = super.dispatchTouchEvent(ev);
				if(!mAnimFinished || !mScrolling) moveBack();
				break;
			}
			return consume;
		}else {
			return super.dispatchTouchEvent(ev);
		}
	}

	private void requestDisallowIntercept() {
		if(!mDisallowInterceptRequest) {
			requestDisallowInterceptTouchEvent(true);
			mDisallowInterceptRequest = true;
		}
	}

	private boolean haveSlideIntention() {	//判定为有滑动意向
		return Math.abs(mScrollByX) >= mTouchSlop;
	}

	@Override
	public boolean onInterceptTouchEvent(MotionEvent ev) {
		if(canSlide()) {
			switch (ev.getAction()) {
			case MotionEvent.ACTION_DOWN:
				return false;	//子View可能需要处理点击事件
			case MotionEvent.ACTION_MOVE:
				//if(mScrolling) return true;	//有一次返回true，则不再继续走该方法
				if(!mTapTimeout) {
					//在点击事件判定时间内，已经有了滑动意向，则拦截，只处理滑动事件，而取消其他事件
					mTapTimeout = SystemClock.elapsedRealtime() - mDownTime >= ViewConfiguration.getTapTimeout();
					if(mTapTimeout) {	//还没有被拦截，那就不拦截了，应该让子View处理点击事件
						//nothing...
					}else {
						if(haveSlideIntention()) return true;
					}
				}
				return false;
			case MotionEvent.ACTION_UP:
			case MotionEvent.ACTION_CANCEL:
				return false;	//都到这里了还没拦截，说明没有滑动意向，就让子View好好处理点击事件吧
			default:
				return false;
			}
		}else {
			return super.onInterceptTouchEvent(ev);
		}
	}

	@Override
	public boolean onTouchEvent(MotionEvent ev) {
		/*当拦截了事件或者Down事件没有子View消化的时候，才会走这里。
		     那么，只要有滚动意向就算滚定，不用等待子View的点击事件超时，判定其是否消化了。*/
		if(!mScrolling && haveSlideIntention()) mScrolling = true;
		if(canSlide()) {
			switch (ev.getAction()) {
			case MotionEvent.ACTION_MOVE:
				doDrag();
				if (mScrolling) {
					return true;
				} else {
					break;
				}
			case MotionEvent.ACTION_UP:
				if(mScrolling) {
					moveToTargetPosition();
					onScrollStarted(mDragDirection, mScrollMode);
					return true;
				}else {
					moveBack();
					break;
				}
			case MotionEvent.ACTION_CANCEL:
				moveBack();
				break;
			default:
				break;
			}
		}else {
			return super.onTouchEvent(ev);
		}
		return super.onTouchEvent(ev);
	}

	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		reset();
		super.onMeasure(widthMeasureSpec, heightMeasureSpec);
	}

	@Override
	protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
		reset();
		super.onLayout(changed, left, top, right, bottom);
		if(canSlide()) {
			mCurrOffsetLeftAndRight = 0;	//必须置为0，而不能让其滚动到该位置，因为此时实际还没有绘制，再次滚动到位置会导致其他情况
			//scrollSlideBy(mCurrOffsetLeftAndRight);
			recomputeEndOnLayout();
		}
	}

	private void reset() {
		mSlideChild = null;
		mSlideOffsetLeft = -1;
		mSlideOffsetRight = 1;
		mWidthMap.clear();
	}

	private void recomputeEndOnLayout() {
		if (!mShaking && !mScroller.isFinished()) mScroller.setFinalX(getScrollTargetX() - mCurrOffsetLeftAndRight);
		//if (mShakeScroller != null && !mShakeScroller.isFinished()) mShakeScroller.	//抖动，最终会归零
	}

	@Override
	public void computeScroll() {
		//mScrolling && 不要这个条件，否则在动画没有结束时，再次快速点击又松开，动画会停止
		if (canSlide() && mScroller.computeScrollOffset()) {
			scrollSlideBy(mScroller.getCurrX() - mCurrOffsetLeftAndRight);
			invalidate();
			if (mScroller.isFinished()) onScrollFinished();
		}
		if (mShakeScroller != null && mShakeScroller.computeScrollOffset()) {
			//这个比较特殊，虽然这个圆形动画差值器，在结束时是慢慢回到起点的，但是最后的一个值是一下子变到终点（Scroller的动作），我们不需要这个终点值
			if (mShakeScroller.isFinished()) {
				scrollSlideBy(- mCurrOffsetLeftAndRight);
				invalidate();
				onShakeFinished();
			} else {
				scrollSlideBy(mShakeScroller.getCurrX() - mCurrOffsetLeftAndRight);
				invalidate();
			}
		}
	}

	public final void smoothScroll() {
		final int dx = getDragTargetX() - mCurrOffsetLeftAndRight;
		//为什么是时间间隔而不是距离间隔？时间间隔太长会有卡顿感，但是通常为了快速响应，无论距离间隔大小都应该即时到位
		long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll;
		if (duration > ANIMATED_SCROLL_GAP) {
			mScroller.startScroll(mCurrOffsetLeftAndRight, 0, dx, 0);
			invalidate();
		} else {
			if (!mScroller.isFinished()) {
				mScroller.abortAnimation();
			}
			scrollSlideBy(dx);
		}
		mLastScroll = AnimationUtils.currentAnimationTimeMillis();
	}

	private void doDrag() {
		stopShakeInner();
		smoothScroll();
	}

	private void moveBack() {
		stopShakeInner();
		moveToTargetPositionInner(true, false);
	}

	private void moveBackDirectly() {
		stopShakeInner();
		moveToTargetPositionInner(true, true);
	}

	private void moveToTargetPosition() {
		stopShakeInner();
		moveToTargetPositionInner(false, false);
	}

	private void moveToTargetDirectly() {
		stopShakeInner();
		moveToTargetPositionInner(false, true);
	}

	private int getScrollTargetX() {
		if (mMode == MODE_DRAG_REMOVE) {
			if(mSlideDirection < 0) {
				return -getWidth();
			}else if(mSlideDirection > 0) {
				return getWidth();
			}else {
				return 0;
			}
		} else {
			if(mSlideDirection < 0) {
				if(mSlideOffsetRight > 0) {	//向左移动，应该<=0
					mSlideOffsetRight = - (mSlideOffsetOnRightView + getRightViewLeftToMyRight(mSlideAlignRightViewId));
				}
				return mSlideOffsetRight;
			}else if(mSlideDirection > 0) {
				if(mSlideOffsetLeft < 0) {	//向右移动，值应该>=0
					mSlideOffsetLeft = mSlideOffsetOnLeftView + getLeftViewRightToMyLeft(mSlideAlignLeftViewId);
				}
				return mSlideOffsetLeft;
			}else {
				return 0;	//目标为起始位置
			}
		}
	}

	private int getDragTargetX() {
		int targetX = mDownScrollX + mScrollByX;
		if (mMode == MODE_STICKY_BACK) {
			int left = mSlideOffsetOnLeftView + getLeftViewRightToMyLeft(mSlideAlignLeftViewId);
			int right = mSlideOffsetOnRightView + getRightViewLeftToMyRight(mSlideAlignRightViewId);
			if (targetX > left) {
				targetX = (int)(left + (targetX - left) * mStickyDivisor);
			} else if (-targetX > right) {
				targetX = (int)(- right - (-targetX - right) * mStickyDivisor);
			}
		}
		return targetX;
	}

	private void moveToTargetPositionInner(boolean back, boolean direct) {
		if(!back && !mShaking) {
			int offset = getDragTargetX();	//相对于0位置的位移
			if (mMode == MODE_DRAG_REMOVE) {
				if(offset < 0) {	//左移动
					mSlideDirection = -offset > getWidth() / 3 ? -1 : 0;
				}else if(offset > 0) {	//右移动
					mSlideDirection = offset > getWidth() / 3 ? 1 : 0;
				}else {
					mSlideDirection = 0;
				}
				mScrollMode = mSlideDirection == 0 ? OnScrollListener.MODE_BACK : OnScrollListener.MODE_REMOVE;
			} else {
				if(offset < 0) {	//左移动
					int right = mSlideOffsetOnRightView + getRightViewLeftToMyRight(mSlideAlignRightViewId);
					mSlideDirection = right == 0 ? 0 : -offset >= right ? -1 : 0;
				}else if(offset > 0) {	//右移动
					int left = mSlideOffsetOnLeftView + getLeftViewRightToMyLeft(mSlideAlignLeftViewId);
					mSlideDirection = left == 0 ? 0 : offset >= left ? 1 : 0;
				}else {
					mSlideDirection = 0;
				}
				mScrollMode = mSlideDirection == 0 ? OnScrollListener.MODE_BACK : OnScrollListener.MODE_STICKY;
			}
			mDragDirection = offset > 0 ? 1 : offset == 0 ? 0 : -1;
		}
		final int dx = getScrollTargetX() - mCurrOffsetLeftAndRight;
		if(dx == 0 || direct) {
			mScroller.abortAnimation();
			scrollSlideBy(dx);
		}else {
			mScroller.startScroll(mCurrOffsetLeftAndRight, 0, dx, 0, Math.max(100, Math.abs((int)(dx * 1.0f/getWidth() * mSlideAnimDuration))));
			invalidate();
		}
	}

	private void scrollSlideBy(int dx) {
		getSlideChild().offsetLeftAndRight(dx);
		mCurrOffsetLeftAndRight += dx;
		if (mShaking) {
			View view;
			if (mCurrOffsetLeftAndRight > 0) {
				view = getLeftViewOrParent();
				if (view != null) view.setVisibility(View.INVISIBLE);	//左侧的显示在右边
				view = getRightViewOrParent();
				if (view != null) view.setVisibility(View.VISIBLE);
			} else if (mCurrOffsetLeftAndRight < 0) {
				view = getLeftViewOrParent();
				if (view != null) view.setVisibility(View.VISIBLE);
				view = getRightViewOrParent();
				if (view != null) view.setVisibility(View.INVISIBLE);
			} else if (mCurrOffsetLeftAndRight == 0) {
				view = getLeftViewOrParent();
				if (view != null) view.setVisibility(View.INVISIBLE);
				view = getRightViewOrParent();
				if (view != null) view.setVisibility(View.INVISIBLE);
			}
		} else if (mMode == MODE_DRAG_REMOVE) {
			if (mCurrOffsetLeftAndRight > 0) {	//&& mCurrOffsetLeftAndRight > getLeftViewRightToMyLeft(mSlideAlignLeftViewId)
				getLeftViewOrParent().setVisibility(View.VISIBLE);
				getRightViewOrParent().setVisibility(View.INVISIBLE);
				int target = mCurrOffsetLeftAndRight - getLeftViewRightToMyLeft(mSlideAlignLeftViewId);
				int min = -(getWidth() - getLeftViewStayOffset());
				if (target < min) {
					target = min;
				}/* else if (target > 0) {
					target = 0;
				}*/
				getDragFollowChild().offsetLeftAndRight(target - mDragChildCurrOffsetLeftAndRight);
				mDragChildCurrOffsetLeftAndRight = target;
			} else if (mCurrOffsetLeftAndRight < 0) {	//&& -mCurrOffsetLeftAndRight > getRightViewLeftToMyRight(mSlideAlignRightViewId)
				getLeftViewOrParent().setVisibility(View.INVISIBLE);
				getRightViewOrParent().setVisibility(View.VISIBLE);
				int target = mCurrOffsetLeftAndRight + getRightViewLeftToMyRight(mSlideAlignRightViewId);
				int max = getWidth() - getRightViewStayOffset();
				if (target > max) {
					target = max;
				}/* else if (target < 0) {
					target = 0;
				}*/
				getDragFollowChild().offsetLeftAndRight(target - mDragChildCurrOffsetLeftAndRight);
				mDragChildCurrOffsetLeftAndRight = target;
			} else if (mCurrOffsetLeftAndRight == 0) {
				getLeftViewOrParent().setVisibility(View.INVISIBLE);
				getRightViewOrParent().setVisibility(View.INVISIBLE);
				getDragFollowChild().offsetLeftAndRight(-mDragChildCurrOffsetLeftAndRight);
				mDragChildCurrOffsetLeftAndRight = 0;
			}
		} else {
			View view;
			if (mCurrOffsetLeftAndRight > 0) {
				view = getLeftViewOrParent();
				if (view != null) view.setVisibility(View.VISIBLE);
				view = getRightViewOrParent();
				if (view != null) view.setVisibility(View.INVISIBLE);
			} else if (mCurrOffsetLeftAndRight < 0) {
				view = getLeftViewOrParent();
				if (view != null) view.setVisibility(View.INVISIBLE);
				view = getRightViewOrParent();
				if (view != null) view.setVisibility(View.VISIBLE);
			} else if (mCurrOffsetLeftAndRight == 0) {
				view = getLeftViewOrParent();
				if (view != null) view.setVisibility(View.INVISIBLE);
				view = getRightViewOrParent();
				if (view != null) view.setVisibility(View.INVISIBLE);
			}
		}
	}

	private int getLeftViewStayOffset() {
		return getView(mSlideAlignLeftViewId).getRight() - getView(mSlideAlignLeftViewLeftSideViewId).getLeft();
	}

	private int getRightViewStayOffset() {
		return getView(mSlideAlignRightViewRightSideViewId).getRight() - getView(mSlideAlignRightViewId).getLeft();
	}

	private View getSlideChild() {
		if(mSlideChild == null && getChildCount() == 2) {
			mSlideChild = getChildAt(1);
		}
		return mSlideChild;
	}

	private View getDragFollowChild() {
		if(mDragChild == null) {
			mDragChild = getChildAt(0);
		}
		return mDragChild;
	}

	private int getLeftViewRightToMyLeft(int id) {
		int left = 0;
		if(id > 0) {
			//left = mOffsetMap.get(id);	//由于view有隐藏和显示操作，涉及到重新布局，因此这里不要缓存
			if (left == 0) {
				View v = getView(id);
				if(v != null) {
					left = v.getRight();
					//if (left != 0) mOffsetMap.put(id, left);
				}
			}
		}
		return left;
	}

	private int getRightViewLeftToMyRight(int id) {
		int right = 0;
		if(id > 0) {
			//right = mOffsetMap.get(id);
			if (right == 0 || right == getWidth()) {
				View v = getView(id);
				if(v != null) {
					right = getWidth() - v.getLeft();
					//if (right != getWidth()) mOffsetMap.put(id, right);
				}
			}
		}
		return right;
	}

	private int getViewWidth(int id) {
		int width = 0;
		if(id > 0) {
			width = mWidthMap.get(id);
			if (width == 0) {
				View v = getView(id);
				if(v != null) {
					width = v.getWidth();
					if (width != 0) mWidthMap.put(id, width);
				}
			}
		}
		return width;
	}

	private View getView(int id) {
		View v = null;
		if (id > 0) {
			v = mViewMap.get(id);
			if (v == null) {
				v = findViewById(id);
				mViewMap.put(id, v);
			}
		}
		return v;
	}

	private View getLeftViewOrParent() {
		View v = getView(mSlideAlignLeftViewParentId);
		if (v == null) v = getView(mSlideAlignLeftViewId);
		return v;
	}

	private View getRightViewOrParent() {
		View v = getView(mSlideAlignRightViewParentId);
		if (v == null) v = getView(mSlideAlignRightViewId);
		return v;
	}

	private int getShakeWidth() {
		return getViewWidth(mShakeWidthReferViewId);
	}

	private void onPreShake() {
		mShaking = true;
		View v = getView(mSlideShakeLeftHideViewId);
		if (v != null) v.setVisibility(GONE);
		v = getView(mSlideShakeRightHideViewId);
		if (v != null) v.setVisibility(GONE);
	}

	private void onShakeFinishInner() {
		View v = getView(mSlideShakeLeftHideViewId);
		if (v != null) v.setVisibility(VISIBLE);
		v = getView(mSlideShakeRightHideViewId);
		if (v != null) v.setVisibility(VISIBLE);
		mShaking = false;
	}

	private void onShakeFinished() {
		onShakeFinishInner();
	}

	private void onShakeAbort() {
		onShakeFinishInner();
	}

	protected void onScrollStarted(int dragDirection, int mode) {
		mDeliverScrollEvent = true;
		if (mOnScrollListener != null) mOnScrollListener.onScrollStarted(this, dragDirection, mode);
	}

	protected void onScrollFinished() {
		if (mDeliverScrollEvent) {
			mDeliverScrollEvent = false;
			if (mOnScrollListener != null) mOnScrollListener.onScrollFinished(this);
		}
	}

	public void setOnScrollListener(OnScrollListener l) {
		mOnScrollListener = l;
	}

	public static interface OnScrollListener {
		/**还原**/
		int MODE_BACK		= 0;
		/**粘滞**/
		int MODE_STICKY		= 1;
		/**移除**/
		int MODE_REMOVE		= 2;

		/**
		 * @param dragDirection 拖动的方向。-1：向左拖动，1：向右拖动
		 * @param mode 是回滚还是移除。true回滚
		 */
		void onScrollStarted(SideSlideView view, int dragDirection, int mode);
		void onScrollFinished(SideSlideView view);
	}
}
